package org.third.integration.ldap.auth;

import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.GreaterThanOrEqualsFilter;
import org.springframework.ldap.filter.LessThanOrEqualsFilter;
import org.springframework.ldap.filter.LikeFilter;
import org.springframework.ldap.filter.NotPresentFilter;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;

import java.net.URI;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Hashtable;

public class LdapCurd {
    // public static final String ldapUrl = "ldap://localhost:10389/";
    public static final String ldapUrl = "ldap://16.187.188.62:389";
    public static final String BASE_DN = "dc=hpeswlab,dc=com";
    // uid=test4,o=TestOrg,dc=my-domain,dc=com

    /**
     * Simple authentication consists of sending the LDAP server the fully qualified DN of the client (user) and the
     * client's clear-text password (see RFC 2251 and RFC 2829). This mechanism has security problems because the
     * password can be read from the network. To avoid exposing the password in this way, you can use the simple
     * authentication mechanism within an encrypted channel (such as SSL), provided that this is supported by the LDAP
     * server.
     * 
     * @return
     */
    public static final Hashtable<String, String> getSimpleAuthenticationContext() {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        // Must use fully qualified hostname
        env.put(Context.PROVIDER_URL, ldapUrl);

        // // Use anonymous authentication
        // env.put(Context.SECURITY_AUTHENTICATION, "none");

        // Authenticate as S. User and password "mysecret"
        env.put(Context.SECURITY_AUTHENTICATION, "simple"); //
        env.put(Context.SECURITY_PRINCIPAL, "cn=Manager," + BASE_DN);
        env.put(Context.SECURITY_CREDENTIALS, "secret");
        // env.put(Context.SECURITY_PRINCIPAL, "cn=test1,ou=Users,ou=TestOrg,"+BASE_DN);
        // env.put(Context.SECURITY_CREDENTIALS, "test1");
        return env;
    }

    public static final Hashtable<String, String> getDigestMd5AuthenticationContext() {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        // Must use fully qualified hostname
        env.put(Context.PROVIDER_URL, ldapUrl);

        // // Use anonymous authentication
        // env.put(Context.SECURITY_AUTHENTICATION, "none");

        // Authenticate as S. User and password "secret"
        env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
        env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
        env.put(Context.SECURITY_CREDENTIALS, "secret");

        return env;
    }

    /**
     * The LDAP v3 protocol uses the SASL to support pluggable authentication. This means that the LDAP client and
     * server can be configured to negotiate and use possibly nonstandard and/or customized mechanisms for
     * authentication, depending on the level of protection desired by the client and the server. The LDAP v2 protocol
     * does not support the SASL.
     * 
     * @return
     * 
     *         <pre>
     * Several SASL(Simple Authentication and Security Layer , is a method for
     *     adding authentication support to connection-based protocols
     *         http://www.ietf.org/rfc/rfc2222.txt) mechanisms are currently defined:
            Anonymous (RFC 2245)
            CRAM-MD5 (RFC 2195)
            Digest-MD5 (RFC 2831)
            External (RFC 2222)
            Kerberos V4 (RFC 2222)
            Kerberos V5 (RFC 2222)
            SecurID (RFC 2808)
            Secure Remote Password (Using the Secure Remote Password (SRP) Protocol for TLS Authentication)
            S/Key (RFC 2222)
            X.509 (draft-ietf-ldapext-x509-sasl-03)
            SASL Mechanisms Supported by LDAP Servers
            
            Attributes attrs = ctx.getAttributes(
                    "ldap://localhost:10389", new String[]{"supportedSASLMechanisms"});
    //            {supportedsaslmechanisms=supportedSASLMechanisms: NTLM, GSSAPI, GSS-SPNEGO, CRAM-MD5, SIMPLE, DIGEST-MD5}
     * 
     * env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");
     * Note: The LDAP provider's GSS-API implementation uses the Java Bindings for GSS-API (RFC 2853) for GSS-API/Kerberos v5 support. 
     * If you are using the Java 2 SDK, v1.4, then the Java GSS and Kerberos implementations are already included so you need to take
     *  no further action. Otherwise, you need to install a Java GSS and Kerberos implementation in order for the examples in this
     *   section to work.
     *         </pre>
     */
    public static final Hashtable<String, String> getSaslAuthenticationContext() {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        // Must use fully qualified hostname
        env.put(Context.PROVIDER_URL, ldapUrl);

        // // Use anonymous authentication
        // env.put(Context.SECURITY_AUTHENTICATION, "none");

        // Authenticate as S. User and password "mysecret"
        env.put(Context.SECURITY_AUTHENTICATION, "simple"); //
        env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
        env.put(Context.SECURITY_CREDENTIALS, "secret");
        return env;
    }

    public static final boolean authenticate(String user, String password) {
        return true;
    }

    public static final boolean searc() {
        try {

            // do something useful with ctxT
            // System.out.println(ctx.lookup("ou=users"));

            javax.naming.directory.SearchControls searchCtls = new javax.naming.directory.SearchControls();
            searchCtls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
            // String searchFilter =
            // "(&(objectClass=person)(cn=*)(uid=ktest1))";]
            String searchFilter = "cn=*";
            // String filter =
            // "(&(modifyTimestamp>=20061231080000Z)(modifyTimestamp<=20070101080000Z))";
            // //条件为 2006年12月31日8点整<=最后一次修改时间<=2007年1月1日8点整，

            org.springframework.ldap.filter.GreaterThanOrEqualsFilter greaterThanEqualFilter = new GreaterThanOrEqualsFilter("manager",
                    "abc");
            org.springframework.ldap.filter.LikeFilter likeFilter = new LikeFilter("manager", "abc");
            org.springframework.ldap.filter.LessThanOrEqualsFilter lessThanEqualFilter = new LessThanOrEqualsFilter("manager", "abc");
            org.springframework.ldap.filter.EqualsFilter equalFilter = new EqualsFilter("manager", "abc");
            org.springframework.ldap.filter.Filter notEqualFilter = new NotPresentFilter("dc");
            NamingEnumeration employees = ctx.search("ou=People,dc=example,dc=com", equalFilter.toString(), searchCtls);

            String searchBase = "ou=Users,o=TestOrg,dc=my-domain,dc=com";
            String returnedAtts[] = {};
            searchCtls.setReturningAttributes(returnedAtts);
            javax.naming.NamingEnumeration<SearchResult> userResult = ctx.search(searchBase, searchFilter, searchCtls);
            while (userResult.hasMoreElements()) {
                SearchResult sr = (SearchResult) userResult.next();
                System.out.println("<<<::[" + sr.getName() + "]::>>>>");
            }

            searchBase = "ou=Roles,o=TestOrg,dc=my-domain,dc=com";
            searchFilter = "roleOccupant=cn=test5,ou=Users,o=TestOrg,dc=my-domain,dc=com";
            javax.naming.NamingEnumeration<SearchResult> roleResult = ctx.search(searchBase, searchFilter, searchCtls);
            System.out.println(ctx.getEnvironment());
            while (roleResult.hasMoreElements()) {
                SearchResult sr = (SearchResult) roleResult.next();
                System.out.println("<<<::[" + sr.getName() + "]::>>>>");
            }

            LdapName fullGroupName = new LdapName("cn=test*");
            SearchControls con = new SearchControls();
            con.setSearchScope(SearchControls.SUBTREE_SCOPE);
            String filter = "({0})";
            Object[] filterArgs = { fullGroupName };
            NamingEnumeration result = ctx.search(searchBase, filter, filterArgs, con);
            System.out.println(result.hasMore());

            while (result.hasMoreElements()) {
                SearchResult sr = (SearchResult) result.next();
                System.out.println("<<<::[" + sr.getName() + "]::>>>>");
                javax.naming.directory.Attributes attrs = sr.getAttributes();
                if (attrs == null || attrs.size() == 0) {
                    System.out.println("No   attributes");
                } else {
                    for (javax.naming.NamingEnumeration<? extends javax.naming.directory.Attribute> attrEnum = sr.getAttributes()
                            .getAll(); attrEnum.hasMore();) {
                        javax.naming.directory.Attribute attr = attrEnum.next();
                        System.out.println(attr.getID() + "=" + attr.get());
                    }
                    javax.naming.directory.Attribute attr = attrs.get("userPassword");
                    if (attr != null) {
                        Object o = attr.get();
                        byte[] s = (byte[]) o;
                        String pwd2 = new String(s);
                        String pwd = "1";
                        try {
                            return (verifySHA(pwd2, pwd));
                        } catch (NoSuchAlgorithmException e) {
                            e.printStackTrace();
                            return false;
                        }
                    }
                }
            }
            // Close the context when we're done
        } catch (NamingException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean verifySHA(String ldappw, String inputpw) throws NoSuchAlgorithmException {

        // MessageDigest 提供了消息摘要算法，如 MD5 或 SHA，的功能，这里LDAP使用的是SHA-1
        MessageDigest md = MessageDigest.getInstance("SHA-1");

        // 取出加密字符
        if (ldappw.toUpperCase().startsWith("{SSHA}")) {
            ldappw = ldappw.substring(6);
        } else if (ldappw.toUpperCase().startsWith("{SHA}")) {
            ldappw = ldappw.substring(5);
        }

        // 解码BASE64
        byte[] ldappwbyte = java.util.Base64.getDecoder().decode(ldappw.getBytes());
        byte[] shacode;
        byte[] salt;

        // 前20位是SHA-1加密段，20位后是最初加密时的随机明文
        if (ldappwbyte.length <= 20) {
            shacode = ldappwbyte;
            salt = new byte[0];
        } else {
            shacode = new byte[20];
            salt = new byte[ldappwbyte.length - 20];
            System.arraycopy(ldappwbyte, 0, shacode, 0, 20);
            System.arraycopy(ldappwbyte, 20, salt, 0, salt.length);
        }

        // 把用户输入的密码添加到摘要计算信息
        md.update(inputpw.getBytes());
        // 把随机明文添加到摘要计算信息
        md.update(salt);

        // 按SSHA把当前用户密码进行计算
        byte[] inputpwbyte = md.digest();

        // 返回校验结果
        return MessageDigest.isEqual(shacode, inputpwbyte);
    }

    public static javax.naming.ldap.InitialLdapContext ctx = null;

    static class AddUserThread implements Runnable {
        String ouDn = "ou=Users,ou=TestOrg,dc=hpeswlab,dc=com";
        int leveUserIndex;

        public AddUserThread(int i) {
            this.leveUserIndex = i;
        }

        public void run() {
            LdapContext ldapContext = null;
            String userTemplate = "test";
            try {
                ldapContext = new javax.naming.ldap.InitialLdapContext(getSimpleAuthenticationContext(), null);
                // if(leveUserIndex==1){
                // addOrganizationUnit(ldapContext, "ou=TestOrg,dc=hpeswlab,dc=com", "Users");
                // }
                //
                addUser(ldapContext, ouDn, userTemplate, 1, leveUserIndex);
                String level1User = userTemplate + leveUserIndex;
                String level1UserDn = "cn=" + level1User + "," + ouDn;
                addUser(ldapContext, level1UserDn, userTemplate, 2000, 10000 * leveUserIndex);
            } catch (NamingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    ldapContext.close();
                } catch (NamingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //
        // // Request the use of the "GSSAPI" SASL mechanism
        // // Authenticate by using already established Kerberos credentials
        // env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
        try {
            /* Create initial context */
            // javax.naming.directory. DirContext ctx = new
            // javax.naming.directory.InitialDirContext(getSimpleAuthenticationContext());
            // ctx = new javax.naming.ldap.InitialLdapContext(getSimpleAuthenticationContext(), null);
            // System.out.println("authenticate Result: " + authenticate("test4", "1"));
            // Close the context when we're done

            // for (int i = 15; i <= 20; i++) {
            // new Thread(new AddUserThread(i)).start();
            // }

            System.err.println(new LdapName(" cnd = as dfj").add("a=c").getSuffix(0));
            System.err.println(new LdapName("fv=90,bg=j").toString());

            System.out.println(new URI(URLEncoder.encode("ldap://www.baidu.com/ dc= a c,dc=99")).toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ctx != null)
                try {
                    ctx.close();
                } catch (NamingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }

        System.gc();
        System.out.println(23);
        System.out.println(23);
        System.out.println(23);
    }

    public void delete() throws NamingException {
        // 条目DN
        String dn = "cn=张三,ou=fin,dc=example,dc=com";
        // 通过LDAP服务器的上下文对象删除一个条目
        this.ctx.destroySubcontext(dn);
    }

    public void update() throws NamingException {
        // 定义条目属性
        Attributes attrs = new BasicAttributes(true);
        // 电话号码
        Attribute telephone = new BasicAttribute("telephoneNumber");
        telephone.add("025-66660000");
        // 手机
        Attribute mobile = new BasicAttribute("mobile");
        mobile.add("15966660000");
        // 设置属性
        attrs.put(telephone);
        attrs.put(mobile);
        // 更新的DN
        String dn = "cn=张三,ou=fin,dc=example,dc=com";
        // 通过LDAP服务器的上下文对象更新一个条目
        this.ctx.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
    }

    public static void addOrganizationUnit(LdapContext ldapContext, String userRelativeDn, String userTemplate) throws NamingException {
        try {
            BasicAttributes basicAttributes = new BasicAttributes(true);
            BasicAttribute objectClassAttribute = new BasicAttribute("objectclass");

            objectClassAttribute.add("top");
            objectClassAttribute.add("organizationalUnit");
            basicAttributes.put(objectClassAttribute);

            String usr = userTemplate;
            basicAttributes.put("ou", usr);

            ldapContext.createSubcontext("ou=" + usr + "," + userRelativeDn, basicAttributes);
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
    }

    public static void addUser(LdapContext ldapContext, String userRelativeDn, String userTemplate, int numberOfUser, int startIndex)
            throws NamingException {
        numberOfUser = Math.max(numberOfUser, 1);
        for (int i = startIndex, size = startIndex + numberOfUser; i < size; i++) {
            try {
                BasicAttributes basicAttributes = new BasicAttributes(true);
                BasicAttribute objectClassAttribute = new BasicAttribute("objectclass");

                objectClassAttribute.add("top");
                objectClassAttribute.add("person");
                objectClassAttribute.add("organizationalPerson");
                objectClassAttribute.add("inetOrgPerson");
                basicAttributes.put(objectClassAttribute);

                String usr = userTemplate + i;
                basicAttributes.put("sn", usr);
                basicAttributes.put("uid", usr);
                basicAttributes.put("userPassword", "1");

                ///// More Attributes
                Attribute manager = new BasicAttribute("manager");
                manager.add(userRelativeDn);
                Attribute telephone = new BasicAttribute("telephoneNumber");
                telephone.add("025-88880000");
                Attribute mobile = new BasicAttribute("mobile");
                mobile.add("15988880000");
                Attribute email = new BasicAttribute("mail");
                email.add(usr + "@test.com");
                email.add(userTemplate + "@test.com");
                basicAttributes.put(manager);
                basicAttributes.put(telephone);
                basicAttributes.put(mobile);
                basicAttributes.put(email);

                basicAttributes.put("businessCategory", "businessCategory__" + usr);
                basicAttributes.put("carLicense", "carLicense__" + usr);
                basicAttributes.put("departmentNumber", "departmentNumber__" + usr);
                basicAttributes.put("description", "description__" + usr);
                basicAttributes.put("destinationIndicator", "destinationIndicator__" + usr);
                basicAttributes.put("displayName", "displayName__" + usr);
                basicAttributes.put("employeeNumber", "employeeNumber__" + usr);
                basicAttributes.put("employeeType", "employeeType__" + usr);
                basicAttributes.put("facsimileTelephoneNumber", "facsimileTelephoneNumber__" + usr);
                basicAttributes.put("givenName", "givenName__" + usr);
                basicAttributes.put("initials", "initials__" + usr);
                basicAttributes.put("l", "l__" + usr);
                basicAttributes.put("labeledURI", "labeledURI__" + usr);
                basicAttributes.put("postalCode", "postalCode__" + usr);
                basicAttributes.put("postOfficeBox", "postOfficeBox__" + usr);
                basicAttributes.put("registeredAddress", "registeredAddress__" + usr);
                basicAttributes.put("st", "st__" + usr);
                basicAttributes.put("street", "street__" + usr);
                basicAttributes.put("telephoneNumber", "telephoneNumber__" + "025-88880000");
                basicAttributes.put("title", "title__" + usr);

                ldapContext.createSubcontext("cn=" + usr + "," + userRelativeDn, basicAttributes);
            } catch (NamingException ex) {
                ex.printStackTrace();
            }
        }

    }

    public static boolean updatePwdLdap(String usr, String pwd) {
        // 4、删除用户，非常简单，只要执行一下
        // ctx.destroySubcontext("cn=" + account); 即可。

        boolean success = false;
        try {
            ModificationItem[] modificationItem = new ModificationItem[1];
            modificationItem[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userPassword", pwd));
            ctx.modifyAttributes("cn=" + usr + ",ou=People", modificationItem);
            ctx.close();
            return true;
        } catch (NamingException ex) {
            ex.printStackTrace();
        }
        return success;
    }
}
